package db

import (
	"context"
	"neo4j-go-try/internal/errors"

	"github.com/neo4j/neo4j-go-driver/v5/neo4j"
	"go.uber.org/zap"
)

type Neo struct {
	slog   *zap.SugaredLogger
	driver neo4j.DriverWithContext
}

type NeoTransaction = neo4j.ManagedTransaction

type ManagedTransactionWork func(tx NeoTransaction) error

func NewNeo(slog *zap.SugaredLogger, uri, user, password string) (n *Neo) {
	ctx := context.Background()
	driver, err := neo4j.NewDriverWithContext(
		uri,                                 // (1)
		neo4j.BasicAuth(user, password, ""), // (2)
	)
	errors.PanicOnErr(err)
	// defer-close
	// defer errors.PanicOnClosureError(ctx, driver)
	// Verify Connectivity
	errors.PanicOnErr(driver.VerifyConnectivity(ctx))
	slog.Infof("neo4j connected!")
	n = &Neo{
		slog,
		driver,
	}
	// defer errors.PanicOnClosureError(ctx, defaultSession)
	return
}

func (s *Neo) Close() {
	// release resources
	ctx := context.Background()
	errors.PanicOnClosureError(ctx, s.driver)
}

func (s *Neo) CreateSession(ctx context.Context, dbName string) neo4j.SessionWithContext {
	session := s.driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: dbName})
	return session
}
func (s *Neo) CloseSession(ctx context.Context, session neo4j.SessionWithContext) {
	errors.PanicOnClosureError(ctx, session)
}

func (c *Neo) convToSessionConfig(settings ...neo4j.ExecuteQueryConfigurationOption) neo4j.SessionConfig {
	configuration := &neo4j.ExecuteQueryConfiguration{}
	for _, setter := range settings {
		setter(configuration)
	}
	return neo4j.SessionConfig{
		ImpersonatedUser: configuration.ImpersonatedUser,
		DatabaseName:     configuration.Database,
		BookmarkManager:  configuration.BookmarkManager,
		BoltLogger:       configuration.BoltLogger,
	}
}

func (s *Neo) ExecuteQuery(
	ctx context.Context,
	cypher string,
	params map[string]any,
	settings ...neo4j.ExecuteQueryConfigurationOption) (res *neo4j.EagerResult, err error) {
	return neo4j.ExecuteQuery(ctx, s.driver, cypher, params, neo4j.EagerResultTransformer, settings...)
}

func (s *Neo) ExecTx(ctx context.Context,
	worker ManagedTransactionWork,
	settings ...neo4j.ExecuteQueryConfigurationOption) (string, error) {
	session := s.driver.NewSession(ctx, s.convToSessionConfig(settings...))
	defer session.Close(ctx)
	r, err := neo4j.ExecuteWrite[string](
		ctx,     // <1>
		session, // <2>
		func(tx neo4j.ManagedTransaction) (string, error) { // <3>
			err := worker(tx)
			if err != nil {
				return "rollback", err
			}
			return "commited", nil
		},
	)
	return r, err
}
